﻿using System.Globalization;
using System.Text;

namespace JanusLibAing
{
    using System;

    /// <summary>
    /// Xtea1 encryption: fast and easy
    /// </summary>
    public class EncryptorXtea
    {
        public const int NumRounds = 24;

        private const int CharSize = sizeof(char);
        private const int Divider = 8 / CharSize;
        private const int CharsInDWord = 4 / CharSize;
        private const int CharBitOffset = CharsInDWord * 8;

        private static UInt32 Rol(UInt32 baseNum, UInt32 shift)
        {
            UInt32 res;

            // only 5 bits of shift are significant
            shift &= 0x1F;
            res = (baseNum << (int) shift) | (baseNum >> (int) (32 - shift));
            return res;
        }

        /// <summary>
        /// Xtea1 encipher.
        /// </summary>
        /// <param name="num_rounds">The num_rounds.</param>
        /// <param name="value">The value - must be 8 bytes.In/Out</param>
        /// <param name="key">The key -must be 16 bytes long</param>
        public void Xtea1Encipher(int num_rounds, UInt32[] value, UInt32[] key)
        {
            int i;
            UInt32 y;
            UInt32 z;
            UInt32 sum = 0;
            UInt32 delta = 0x9E3779B9;

            // load and pre-white the registers 
            y = value[0] + key[0];
            z = value[1] + key[1];

            // Round functions 
            for (i = 0; i < num_rounds; i++)
            {
                y += ((z << 4) ^ (z >> 5)) + (z ^ sum) + Rol(key[sum & 3], z);
                sum += delta;
                z += ((y << 4) ^ (y >> 5)) + (y ^ sum) + Rol(key[(sum >> 11) & 3], y);
            }

           // post-white and store registers 
            value[0] = y ^ key[2];
            value[1] = z ^ key[3];
        }

        /// <summary>
        /// Xtea1 decipher.
        /// </summary>
        /// <param name="num_rounds">The num_rounds.</param>
        /// <param name="value">The value - must be 8 bytes. In/Out</param>
        /// <param name="key">The key - must be 16 bytes long (4 x 32bits)</param>
        public void Xtea1Decipher(int num_rounds, UInt32[] value, UInt32[] key)
        {
            int i;
            UInt32 y;
            UInt32 z;
            UInt32 delta = 0x9E3779B9;
            UInt32 sum = (UInt32)(delta * num_rounds);

            z = value[1] ^ key[3];
            y = value[0] ^ key[2];
            for (i = 0; i < num_rounds; i++)
            {
                z -= ((y << 4) ^ (y >> 5)) + (y ^ sum) + Rol(key[(sum >> 11) & 3], y);
                sum -= delta;
                y -= ((z << 4) ^ (z >> 5)) + (z ^ sum) + Rol(key[sum & 3], z);
            }

            value[1] = z - key[1];
            value[0] = y - key[0];
        }

        /// <summary>
        /// Encrypts the specified text.
        /// </summary>
        /// <param name="text">The original text.</param>
        /// <param name="key">The key - must be 16 bytes long</param>
        /// <returns>The encrypted text.</returns>
        public string Encrypt(string text, uint[] key)
        {
            StringBuilder ret = new StringBuilder();
            if (!String.IsNullOrEmpty(text))
            {
                uint[] value = new uint[2];

                // convert to nearest biggest value % 8
                int normalizedLenght = ((text.Length + Divider - 1) / Divider) * Divider;
                if (CharSize == 2)
                {
                    for (int i = 0; i < normalizedLenght; i += Divider)
                    {
                        int shift;

                        value[0] = 0;
                        value[1] = 0;

                        for (int j = 0; j < Divider; j++)
                        {
                            shift = (CharBitOffset * j) % 32;
                            char curChar = ' ';
                            int charIndex = i + j;
                            if (charIndex < text.Length)
                            {
                                curChar = text[charIndex];
                            }

                            if (j < CharsInDWord)
                            {
                                value[0] <<= shift; // 0, 16
                                value[0] |= curChar; // 0, 16
                            }
                            else
                            {
                                value[1] <<= shift; // 0, 16
                                value[1] |= curChar; // 0, 16
                            }
                        }

                        Xtea1Encipher(NumRounds, value, key);
                        ret.Append(value[0].ToString("X8"));
                        ret.Append(value[1].ToString("X8"));
                    }
                }
                else
                {
                    //don't need now. Ignore. IT is possible that code before will be universal
                }
            }

            return ret.ToString();
        }

        /// <summary>
        /// Decrypts the specified text.
        /// </summary>
        /// <param name="text">The encrypted text.</param>
        /// <param name="key">The key - must be 16 bytes long</param>
        /// <returns>Decoded text</returns>
        public string Decrypt(string text, uint[] key)
        {
            StringBuilder ret = new StringBuilder();

            if (!String.IsNullOrEmpty(text) && (text.Length % 16) == 0)
            {
                uint[] value = new uint[2];
                int mask = 0xFFFF;
                if (CharsInDWord == 1)
                {
                    mask = 0xFF;
                }

                for (int i = 0; i < text.Length; i += 16)
                {
                    string part0 = text.Substring(i, 8);
                    string part1 = text.Substring(i + 8, 8);
                    value[0] = uint.Parse(part0, NumberStyles.HexNumber);
                    value[1] = uint.Parse(part1, NumberStyles.HexNumber);

                    Xtea1Decipher(NumRounds, value, key);
                    for (int j = 0; j < Divider; j++)
                    {
                        int shift = (CharBitOffset * (j+1)) % 32;

                        char curChar;
                        if (j < CharsInDWord)
                        {
                            curChar = (char)((value[0] >> shift) & mask); // shift 16,0
                        }
                        else
                        {
                            curChar = (char)((value[1] >> shift) & mask); // 16,0
                        }

                        ret.Append(curChar);
                    }
                }
            }

            return ret.ToString().Trim();
        }
    }
}

/*
 * http://code.google.com/p/sharpot/source/browse/trunk/sharpot/Packets/Xtea.cs?r=181 cs
 * http://www.shokhirev.com/nikolai/programs/code/Cryptography/uTeaSet_pas.html
 * 
 const
  Delta: longword = $9e3779b9;

type

  TLong2 = array[0.. 1] of Longword;  // 64-bit
  TTeaKey = array[0..3] of longword;  // 128-bit
  TByte16 = array[0..15] of byte;     // 128-bit
  TByte4 = array[0..3] of byte;       // 32-bit
  TTeaData = array of longword;       // n*32-bit
  TByteArrData = array of byte;       // n*8-bit

// Algorithm: David Wheeler & Roger Needham, Cambridge University Computer Lab
// TEA:       http://www.cl.cam.ac.uk/ftp/papers/djw-rmn/djw-rmn-tea.html (1994)
procedure TeaEncrypt(var data: TLong2; const key: TTeaKey);
procedure TeaDecrypt(var data: TLong2; const key: TTeaKey);

// Algorithm: David Wheeler & Roger Needham, Cambridge University Computer Lab
// XTEA: http://www.cl.cam.ac.uk/ftp/users/djw3/xtea.ps (1997)
procedure XTeaEncrypt(var data: TLong2; const key: TTeaKey; N: Longword = 32);
procedure XTeaDecrypt(var data: TLong2; const key: TTeaKey; N: Longword = 32);

// Algorithm: David Wheeler & Roger Needham, Cambridge University Computer Lab
// Block TEA: http://www.cl.cam.ac.uk/ftp/users/djw3/xtea.ps (1997)
procedure BlockTeaEncrypt(data: TTeaData; const key: TTeaKey);
procedure BlockTeaDecrypt(data: TTeaData; const key: TTeaKey);

// Algorithm: David Wheeler & Roger Needham, Cambridge University Computer Lab
// XXTEA:     http://www.cl.cam.ac.uk/ftp/users/djw3/xxtea.ps (1998)
procedure XXTeaEncrypt(data: TTeaData; const key: TTeaKey);
procedure XXTeaDecrypt(data: TTeaData; const key: TTeaKey);

// comparison of TTeaKey type variables
function SameKey(const key1, key2: TTeaKey): boolean;

// Conversion routines
procedure StrToKey(const s: string; var key: TTeaKey);
function KeyToStr(const key: TTeaKey): string;

// Conversion routines
procedure StrToData(const s: string; var data: TTeaData);
procedure DataToStr(var s: string; const data: TTeaData);

// reads a file of longword
procedure ReadData(const FileName: string; var data: TTeaData);
// writes a file of longword
procedure WriteData(const FileName: string; var data: TTeaData);

implementation

uses
  math, SysUtils;


procedure XTeaEncrypt(var data: TLong2; const key: TTeaKey; N: Longword = 32);
var
  y,z,sum,limit: Longword;
begin
  y:=data[0];
  z:=data[1];
  sum:=0;
  limit := Delta*N;
  while sum <> limit do
  begin
{ c code:
      y += (z << 4 ^ z >> 5) + z ^ sum + key[sum&3];
      sum += delta;
      z += (y << 4 ^ y >> 5) + y ^ sum + key[sum>>11 & 3];
}
//    inc(y,((z shl 4) xor (z shr 5)) xor (sum+key[sum and 3]));
    inc(y,(((z shl 4) xor (z shr 5)) + z) xor (sum+key[sum and 3]));
    inc(sum,Delta);
//    inc(z,((y shl 4) xor (y shr 5)) xor (sum+key[(sum shr 11) and 3]));
    inc(z,(((y shl 4) xor (y shr 5)) + y) xor (sum+key[(sum shr 11) and 3]));
  end;
  data[0]:=y;
  data[1]:=z
end;

procedure XTeaDecrypt(var data: TLong2; const key: TTeaKey; N: Longword = 32);
var
  y,z,sum: Longword;
begin
  y:=data[0];
  z:=data[1];
  sum:=Delta*N;
  while sum <> 0 do
  begin
{ c code:
      z -= (y << 4 ^ y >> 5) + y ^ sum + key[sum>>11 & 3];
      sum -= delta;
      y -= (z << 4 ^ z >> 5) + z ^ sum + key[sum&3];
}
//    dec(z,((y shl 4) xor (y shr 5)) xor (sum+key[(sum shr 11) and 3]));
    dec(z,(((y shl 4) xor (y shr 5)) + y) xor (sum+key[(sum shr 11) and 3]));
    dec(sum,Delta);
//    dec(y,((z shl 4) xor (z shr 5)) xor (sum+key[sum and 3]));
    dec(y,(((z shl 4) xor (z shr 5)) + z) xor (sum+key[sum and 3]));
  end;
  data[0]:=y;
  data[1]:=z
end;

function SameKey(const key1, key2: TTeaKey): boolean;
var
  i: integer;
begin
  result := false;
  for i := 0 to 3 do
    if key1[i] <> key2[i] then
      exit;
  result := true;
end;

procedure StrToKey(const s: string; var key: TTeaKey);
var
  sa, sb: AnsiString;
  i, n: integer;
begin
  sa := AnsiString(s);
  sb := StringOfChar(' ',16);
  n := min(Length(sa),16);
  for i := 1 to n do
    sb[i] := sa[i];
  for i := 1 to 16 do
    TByte16(key)[i-1] := ord(sb[i]);
  sa := '';
  sb := '';
end;

function KeyToStr(const key: TTeaKey): string;
var
  s: AnsiString;
  i: integer;
begin
  SetLength(s,16);
  for i := 1 to 16 do
  begin
    s[i] := Chr(TByte16(key)[i-1]);
  end;
  result := s;
end;

procedure StrToData(const s: string; var data: TTeaData);
var
  sa: AnsiString;
  i, n, m: integer;
begin
  sa := AnsiString(s);
  n := Length(sa) div 4;
  m := Length(sa) mod 4;
  if m <> 0 then
  begin
    inc(n);
    sa := sa + StringOfChar(' ',m);
  end;
  if n < 2 then  // n = 1
  begin
    n := 2;
    sa := sa + StringOfChar(' ',4);
  end;

  SetLength(data,n);
  for i := 0 to n-1 do
    for m := 0 to 3 do
      TByte4(data[i])[m] := ord(sa[i*4+m+1]);
  sa := '';
end;

procedure DataToStr(var s: string; const data: TTeaData);
var
  sa: AnsiString;
  i, n, m: integer;
  b: byte;
begin
  n := Length(data);
  SetLength(sa,n*4);
  for i := 0 to n-1 do
    for m := 0 to 3 do
    begin
      b := TByte4(data[i])[m];
      sa[i*4+m+1] := Chr(b);
    end;
  s := Trim(sa);
  sa := '';
end;

procedure ReadData(const FileName: string; var data: TTeaData);
  var
    i, n: integer;
    ww: longword;
    wwf: file of longword;
begin
  try
    AssignFile(wwf,FileName);
    Reset(wwf);
    n := FileSize(wwf);
    SetLength(data,n);
    for i := 0 to n-1 do
    begin
      read(wwf,ww);
      data[i] := ww;
    end;
  finally
    CloseFile(wwf);
  end;
end;

procedure WriteData(const FileName: string; var data: TTeaData);
var
  i, n: integer;
  ww: longword;
  wwf: file of longword;
begin
  try
    AssignFile(wwf,FileName);
    Rewrite(wwf);
    n := Length(data);
    for i := 0 to n-1 do
    begin
      ww := data[i];
      write(wwf,ww);
    end;
  finally
    CloseFile(wwf);
  end;
end;
*/